home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Developer CD v1.1
/
Amiga Developer CD v1.1 - May 1996 (1996)(Schatztruhe)[!].iso
/
Contributions
/
IAM
/
Networking
/
Envoy-2.0
/
obs
/
Valuable_Concepts
< prev
next >
Wrap
Text File
|
1994-12-22
|
25KB
|
554 lines
/*------------------------------------------------------------------------*/
/* *
* $Id: Valuable_Concepts 1.1 1994/11/29 13:36:31 heinz Exp $
* */
/*------------------------------------------------------------------------*/
nipc.library
============
NIPC is a new library designed to provide a high level interface to
networking hardware. By using NIPC, programmers will not have to deal
with all of the issues involved in networked communications such as
sequencing, reliability, etc. NIPC has also been designed to be both
easy to use and yet very powerful.
The NIPC API was designed in such a way that it would seem familiar to
Amiga programmers that are familiar with Exec Messages and Device I/O.
Many NIPC function calls such as GetTransaction(), DoTransaction() and
ReplyTransaction() strongly resemble function calls already present in
the Amiga OS.
At the lowest level, NIPC uses standard SANA-II device drivers so that
it is capable of running on virtually any networking hardware,
including ARCNET, Ethernet and even serial ports via SLIP.
NIPC transfers data between machines with primarily two concepts -
"Entities" and "Transactions".
Entities and Transactions
=========================
An Entity is nothing more than a communications "endpoint". Any data
sent using NIPC is sent between two of these endpoints - between two
Entities. An Entity is known by two characteristics: it's name (an
ASCII string of up to 63 characters plus a terminating NULL), and the
name of the machine on which it resides. These Entities are
guaranteed uniqueness by the fact that NIPC does not allow two
Entities to exist on the same machine with the same name.
A given machine can have an unlimited number of Entities, each
communicating with an unlimited number of other Entities. NIPC allows
communication between Entities by establishing communications "paths"
between individual Entities. These paths are considered
unidirectional in the sense that only the Entity that created the
communications path can initiate a data transfer. However, the
"paths" are also bidirectional in the sense that data can flow in both
directions. In the diagram below, four Entities named 'A', 'B', 'C'
and 'D' exist with various lines connecting the Entities together.
Each of the lines indicates one of these data paths. In the case
below, Entity A has a path to Entity C and also has a path to B. B
has a path to D and also a path back to A.
- -
|A| ----------------> |B|
- <---------------- -
| |
| |
| |
| |
V V
- -
|C| |D|
- -
A data path is created by one Entity attempting to locate, or 'find'
another Entity. This is done with the library function FindEntity().
Conversely, a data path is removed by the library function
LoseEntity(). These data paths exist only as long as the creator of
the path wishes it to exist (or until one side of the path disappears
for some reason).
Data is transferred from one point to another in Transactions. A
Transaction is defined by a structure called the Transaction
structure. These are allocated and freed by the library functions
AllocTransaction() and FreeTransaction().
A Transaction is always initiated by a program that has previously
created a data path using the FindEntity() function. The transaction
is then sent to a destination Entity, which deals with it however it
wishes. The transaction is then returned to the sender. As said
before, these data paths are somewhat unidirectional. In the above
diagram, Entity A is allowed to send a Transaction to Entity C - who
will receive it and send it back. However, since Entity C has no data
path to Entity A - it cannot initiate a Transaction. However, both
Entity A and Entity B established seperate data paths to each other
allowing either to initiate a Transaction to the other.
A Transaction does allow for bidirectional data flow, however. The
sender of a Transaction can attach "Request data" to it that the
receiver can read. The receiver is allowed to place data into a
"Response buffer" and return that data back to the sender.
A Transaction can at any time be in one of three phases: the Request
phase, the Servicing phase, and the Response phase.
The following diagrams may be useful in understanding each of these
phases. In the diagram below, one machine is about to communicate
with another machine. A program on machine 1 has succeeded at a
FindEntity(), thereby locating and connecting to Entity B on Machine
2. Now that the program has found Entity B, it attempts to begin a
Transaction by using either BeginTransaction() or DoTransaction().
Once the program has Begin/DoTransaction()'d, NIPC transmits the
Request data portion of the Transaction to Machine 2. This phase of
the Transaction is the Request phase, and is shown in Diagram 1.
When the Transaction is received by NIPC on Machine 2, it is placed
onto the destination Entity - Entity B in this example. While it
remains on this Entity, the Transaction remains in the Request phase.
Once the server program on Machine 2 attempts a GetTransaction() on
that Entity and removes the given Transaction from the Entity, the
Transaction enters the Servicing phase. This is shown in Diagram 2.
While the server program attempts to fufill the needs of the
Transaction the Transaction remains in the Servicing phase. When
the server program has finished with the Transaction and attempts
to return it to Machine 1 by calling ReplyTransaction(), the
Transaction enters the Response phase. The response data is
transmitted back to the original sender (Machine 1) and the
Transaction is returned to the source Entity (Entity A) as shown
in Diagram 3. The Transaction remains in this phase forever - or
until it is reused. The trans_Type field indicates which phase a
given Transaction is in.
MACHINE 1 MACHINE 2
______ ______
|Entity| Request Data |Entity|
| A | -------------> | B |
|______| |______|
Request Phase
Diagram 1.
MACHINE 1 MACHINE 2
______ ______
|Entity| |Entity| (Server has
| A | | B | Transaction)
|______| |______|
Servicing Phase
Diagram 2.
MACHINE 1 MACHINE 2
______ ______
|Entity| Response Data |Entity|
| A | <------------ | B |
|______| |______|
Response Phase
Diagram 3.
Analogies and Similarities
==========================
NIPC shares many concepts with Exec messages and device I/O. One such
concept is the idea that applications communicate with other
applications or devices by sending and receiving finite pieces of
data, usually in the form of an Exec Message. NIPC also shares the
concept of a communications endpoint similar to the Exec Message Port.
In NIPC, the communications endpoint is known as an Entity. Entities
are used as both the source and destinations of data being sent
between two applications. The data being sent between applications is
known in NIPC as a Transaction.
Shown below are two diagrams that illustrate how Messages and Ports
relate to Transactions and Entities.
Application<-->MsgPort<----Message----->MsgPort<-->Application
Application<-->Entity<---Transactions--->Entity<-->Application
Unfortunately, it gets more complicated than the above diagram can
show. Unlike Exec Messages, NIPC Transactions are not guaranteed to
arrive at a destination Entity in a preset amount of time. In fact,
it is possible that a Transaction will never make it to it's
destination due to machine and/or network failures. NIPC does make a
good effort at delivering transactions, however. The lower level
protocols used in NIPC help to provide this functionality.
Another difference between NIPC communications and Exec Messages is
that NIPC is designed around a client-server model. In many cases the
two applications communicating will be a client and a server, such as
the case with a print spooler or a network filesystem. Because of
this, clients have more control over the transactions than the servers
do. This will become apparent later on.
Using NIPC
==========
Before communication via NIPC can begin, each application that will be
involved must create an Entity. This is done by calling
CreateEntity(). Once an application has created it's own entity, it
must find the other application's entity. Typically, the entity to be
found is a public Entity created by a server.
To find an Entity created by another program, you call FindEntity().
FindEntity() has parameters that allow you to specify the name of the
Entity, the name of the host that you are trying to find the Entity
on, and the Entity that you will be using for the source of your
Transactions.
The reason that FindEntity() also requires a pointer to your program's
Entity is that NIPC communication is connection oriented. This means
that a connection forms between your Entity and the Entity you found.
This connection will continue to exist until you no longer need it or
until a permanent network failure or machine failure occurs. When you
are done communicating with the other Entity, you call LoseEntity().
The concept of a connection does not exist with Exec Messages. This
is why you must use a Forbid()/Permit() pair when communicating with
public message ports to guarantee that the port does not go away in
between the time you call FindPort() and PutMsg().
Once you have created your entity and found the remote entity, you
must allocate a Transaction structure. The only supported way of
doing this is to call the NIPC function AllocTransaction().
AllocTransaction() allows you to optionally specify buffer sizes for
any data being sent and/or any data that may be received that the
Transaction is complete. You may also allocate your own buffers, but
you are responsible for freeing them yourself.
A Transaction is sent on it's way by calling either BeginTransaction()
or DoTransaction(). BeginTransaction() will return immediately unless
you are trying to send more data than the network protocols are
capable of handling at the moment. Once your Transaction has begun,
BeginTransaction() will return. It should be noted that the amount of
time that BeginTransaction() will block will usually be quite small.
Therefore, BeginTransaction() should be considered an asynchronous
call.
DoTransaction() will block until either the Transaction has completed
or an error occured. Because your program will not be able to
anything until DoTransaction() returns, it is strongly suggested that
you try to avoid it, or spawn a separate task or process to call
DoTransaction().
When the Transaction returns, you should examine it to determine how
much, if any, data was returned and whether or not an error condition
occured.
Programs acting as servers usually do not call AllocTransaction(),
BeginTransaction() or DoTransaction(). Typically, a server will wait
for a Transaction to arrive at it's public Enitity. This may be done
by using either WaitEntity() or by using the Exec Wait() function. To
see if a Transaction is available at your Entity, you should call
GetTransaction() which will eithe return a pointer to a Transaction or
NULL if there are not more transactions to be processd.
For each transaction you receive, you should examine it to determine
what you should do with the data in it, or if you need to return some
data to a client. Once you have finished processing the transaction,
you must return it to it's sender by calling ReplyTransaction().
For an example NIPC client and server, please see the two files named
RemoteRequest.c and RequestServer.c. These two programs allow you to
put up a requester on a remote machine running NIPC.
Structure and Function call Overview
====================================
While the text below will give you a useful introducation into what is
needed to use nipc.library, you should in any case refer to the
autodocs and include files for nipc.library to get the complete
picture. Only the most important things are outlined here and some
stuff is omitted.
struct Transaction
{
struct Message trans_Msg;
struct Entity *trans_SourceEntity;
struct Entity *trans_DestinationEntity;
UBYTE trans_Command;
UBYTE trans_Type;
ULONG trans_Error;
ULONG trans_Flags;
ULONG trans_Sequence;
APTR trans_RequestData;
ULONG trans_ReqDataLength;
ULONG trans_ReqDataActual;
APTR trans_ResponseData;
ULONG trans_RespDataLength;
ULONG trans_RespDataActual;
UWORD trans_Timeout;
};
trans_Msg
Used internally by nipc.library. Do not use or examine.
trans_SourceEntity
This is the entity from which the Transaction originated.
This is filled in by Begin/DoTransaction(). It may be
examined by the responder. The common use of this field is
for a responder who has just received a Transaction from
another Entity to pass into GetHostName() or GetEntityName() -
to determine the name of the Entity that sent the Transaction,
and the name of the machine from which that Transaction
originiated. Note that this must be done before replying the
Transaction. The responder must not cache the value.
trans_DestinationEntity
Used internally by nipc.library. Do not use or examine.
trans_Command
This field is filled in by the originator of the Transaction
and may be examined by the responder. It is generally only
read by the responder. This field is generally used to
indicate the particular action the originator desires the
responder to perform. It is analogous to the IORequest
io_Command field. Note that, unlike with Exec Device I/O,
there are no standard responder commands which all requesters
can expect all responders to understand.
trans_Type
One of TYPE_REQUEST, TYPE_RESPONSE or TYPE_SERVICING. A
Transaction is always in one of three phases - the Request
phase, between the time it is Begin/DoTransaction()'d and the
time the server GetTransaction()'s it from it's destination
Entity; the Servicing phase, between GetTransaction()'ing it,
and ReplyTransaction()'ing it; and lastly the Response phase,
which begins when the Transaction is returned to it's Source
Entity. The most common use of this field is to determine if a
Transaction obtained from GetTransaction() is a Request from
another Entity, or merely a returning Transaction that was
originally sent from this Entity.
trans_Error
Both nipc.library and the user may write and examine this
field. It is cleared (set to ENVOYERR_NOERROR) on a call to
Do/BeginTransaction(). If the Transaction can be successfully
copied locally or over the network, any value placed in this
field by the responder will be here. If the Transaction
cannot be copied for some reason, nipc.library will place it's
error code here. Common error codes are defined in
<envoy/errors.h>. Human-understandable text errors can be
obtained from these error numbers with a call to envoy.library
[NOT YET AVAILABLE].
trans_Flags
This field is a series of nipc.library flags. These flags
indicate several things, including who is responsible for
freeing buffers. Flags should not be set or cleared by the
user except through NIPC functions or as permitted by comments
in the Includes.
trans_Sequence
Used internally by nipc.library. Do not use or examine.
trans_RequestData
Pointer to any data needed from the requester by the
responder. This may be NULL if trans_ReqDataActual is zero,
indicating that no request data was necessary. The responder
should make no changes to data in this buffer! Programs which
do will behave differently in networked vs. local
Transactions. See the GetTransaction() autodoc for more
details.
trans_ReqDataLength
Length of the buffer pointed to by trans_RequestData. It is
eventually used by the allocator of the buffer to free the
buffer.
trans_ReqDataActual
Length of data in the buffer pointed to by trans_RequestData
which will actually be used by the responder. This allows
re-used Transaction structures to have large buffers which
aren't copied across the network if the whole buffer isn't
filled with data. This may be NULL if no data is to be sent
to the responder.
trans_ResponseData
Pointer to a buffer into which any reply will be placed by the
responder. This may be NULL if trans_RespDataLength is NULL.
Currently this must be allocated by the requester, either at
AllocTransaction() time or subsequently prior to
Do/BeginTransaction().
trans_RespDataLength
Length of the buffer pointed to by trans_ResponseData. It is
eventually used by the allocator of the buffer to free the
buffer. The allocator of the buffer is never the responder.
trans_RespDataActual
Length of the data in the buffer pointed to by
trans_ResponseData actually placed there by the responder.
This allows the responder to specify that the information it
is returning is smaller than the buffer provided in order to
avoid unnecessary copying unused buffer across the network.
trans_Timeout
This is the value in seconds that the requester expects as the
maximum time for the responder to spend processing the
request. nipc.library will add to this value the maximum time
it expects the transfer of data between the Entities to take
(this is variable depending on the distance of the machines
communicating). If a Transaction exceeds the total timeout
and NIPC can abort the Transaction, it will be returned to the
requester with trans_Error set to ENVOYERR_TIMEOUT. Though no
timeout will be used if trans_Timeout is set to zero, most
Transactions should have a timeout. If there is no timeout, a
Transaction received by the responder but not replied (i.e.,
because the machine crashed before getting to the reply) would
never be returned (unless the requester called
AbortTransaction()). Thus, all programs should specify a
Timeout other than zero or eventually call AbortTransaction()
themselves.
On the requester's side, Transactions are moved around with
nipc.library functions which look and act much like Exec
Device I/O functions. Instead of DoIO(), WaitIO(), CheckIO(),
AbortIO() and BeginIO(), nipc.library has DoTransaction(),
WaitTransaction(), CheckTransaction(), AbortTransaction() and
BeginTransaction().
DoTransaction(dest_entity, src_entity, transaction)
an easy-to-use Transaction function. It initiates a
Transaction and waits for its completion. This is a
synchronous function; it does not return until completion of
the Transaction. If the Transaction never completes, this
call never returns. Non-trivial programs should avoid the use
of this function in order to continue processing break signals
or other user input. Use BeginTransaction() and Wait() on the
Entity's signal bit instead.
BeginTransaction(dest_entity, src_entity, transaction)
initiates a Transaction without waiting for completion. This
is an asynchronous Transaction; control returns immediately.
Use GetTransaction() on src_entity to get the replied
Transaction.
transaction = GetTransaction(entity)
Gets replied Transactions off of your source Entity. You
should use Wait() or WaitTransaction() rather than
continuously polling. Once woken up from a
Wait[Transaction](), you should loop on GetTransaction() while
it returns something other than NULL to get all Transactions
off the Entity.
WaitTransaction(transaction)
Waits for the completion of a previously initiated
asynchronous Transaction. This function will not return
control until the Transaction has completed (successfully or
unsuccessfully). Programs should not WaitTransaction() on a
Transaction that has not actually been sent.
status = CheckTransaction(transaction)
Examines a previously queued Transaction, and returns
information on whether it has completed or not.
AbortTransaction(transaction)
attempts to cancel a Transaction. AbortTransaction() may
fail; if it succeeds, the Transaction is replied by NIPC
earlier than it may otherwise have been replied. Programs must
wait for the reply before actually reusing the Transaction.
AbortTransaction() takes no action on Transactions which are
already completed.
The responder's side of a Transaction uses functions which are
similar to those that manipulate Exec Ports and Messages.
Rather than WaitPort(), GetMsg() and ReplyMsg(), there are
WaitEntity(), GetTransaction() and ReplyTransaction().
WaitEntity(localentity)
Waits for a Transaction to arrive at an Entity. Non-trivial
programs should avoid using this function in order to continue
processing break signals or other user input. Use Wait() on
the Entity's signal bit instead.
transaction = GetTransaction(entity)
Transactions must be removed from Entities on the responder's
side of a Transaction with a call to GetTransaction().
Once a responder is done processing a Transaction, it must
return the Transaction to the requester using the
ReplyTransaction() function.
Unlike Exec Ports which can be created manually or with an
amiga.lib function, Entities must be created and destroyed
with nipc.library or services.library functions.
myentity = CreateEntity(tag1, ...)
creates a new Entity which will can be used for calls to
WaitEntity() and GetTransaction(). Entities created with
CreateEntity() must eventually be deleted with DeleteEntity().
DeleteEntity(entity)
deletes an Entity created by CreateEntity(). Do not use on
Entities found with FindEntity() (see below).
Although the Entity structure is not publicly defined, two
functions are provided to return some information about a given
Entity.
success = GetHostName(entity, stringptr, available)
provides the name of the host on which a given Entity was
created. This is useful to responders that need to know which
machines they are responding to.
success = GetEntityName(entity stringptr available)
provides the name of the Entity. This is primarily useful to
responders that need to know the name of the Entities to which
they are responding.
remoteentity = FindEntity(host, entityname, src_entity, errptr)
Finds an Entity created by CreateEntity(). This Entity may be
used as the destination of a Do/BeginTransaction(). Entities
found with FindEntity() must eventually be forgotten with a call
to LoseEntity().
LoseEntity(entity)
Forgets about an Entity found with a call to FindEntity().